#ifndef __BUILD_PACKET_H__
#define __BUILD_PACKET_H__

#include "log.h"
#include <errno.h>
#include <fcntl.h>
#include "endian_portable.h"
#include "list.h"
#if defined(WIN32)
#include <ws2tcpip.h>
#endif
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <string.h>

#define GET_IPH(p)  ((struct my_iphdr     *)((p)->data + (p)->l3_offset))
#define GET_TCPH(p) ((struct my_tcphdr    *)((p)->data + (p)->l4_offset))
#define GET_UDPH(p) ((struct my_udphdr    *)((p)->data + (p)->l4_offset))
#define tcp_seq_h(q) be32toh((GET_TCPH(q)->seq))

struct my_udphdr
{
  __extension__ union {
    struct {
      uint16_t uh_sport;	/* source port */
      uint16_t uh_dport;	/* destination port */
      uint16_t uh_ulen;		/* udp length */
      uint16_t uh_sum;		/* udp checksum */
    };
    struct {
      uint16_t source;
      uint16_t dest;
      uint16_t len;
      uint16_t check;
    };
  };
}__attribute__((packed));

struct my_icmphdr {
    uint8_t type;
    uint8_t code;
    uint16_t checksum;
    union {
        struct {
            uint16_t id;
            uint16_t sequence;
        } echo;
        uint32_t gateway;
        struct {
            uint16_t unused;
            uint16_t mtu;
        } frag;
        uint8_t reserved[4];
    } un;
}__attribute__((packed));

typedef uint32_t tcp_seq;
struct my_tcphdr {
    __extension__ union {
        struct {
            uint16_t th_sport;  /* source port */
            uint16_t th_dport;  /* destination port */
            tcp_seq th_seq;     /* sequence number */
            tcp_seq th_ack;     /* acknowledgement number */
# if __BYTE_ORDER == __LITTLE_ENDIAN
            uint8_t th_x2:4;    /* (unused) */
            uint8_t th_off:4;   /* data offset */
# elif __BYTE_ORDER == __BIG_ENDIAN
            uint8_t th_off:4;   /* data offset */
            uint8_t th_x2:4;    /* (unused) */
# endif
            uint8_t th_flags;
# define TH_FIN 0x01
# define TH_SYN 0x02
# define TH_RST 0x04
# define TH_PUSH    0x08
# define TH_ACK 0x10
# define TH_URG 0x20
            uint16_t th_win;    /* window */
            uint16_t th_sum;    /* checksum */
            uint16_t th_urp;    /* urgent pointer */
        };
        struct {
            uint16_t srcport;
            uint16_t dstport;
            uint32_t seq;
            uint32_t ack_seq;
# if __BYTE_ORDER == __LITTLE_ENDIAN
            uint16_t res1:4;
            uint16_t doff:4;
            uint16_t fin:1;
            uint16_t syn:1;
            uint16_t rst:1;
            uint16_t psh:1;
            uint16_t ack:1;
            uint16_t urg:1;
            uint16_t res2:2;
# elif __BYTE_ORDER == __BIG_ENDIAN
            uint16_t doff:4;
            uint16_t res1:4;
            uint16_t res2:2;
            uint16_t urg:1;
            uint16_t ack:1;
            uint16_t psh:1;
            uint16_t rst:1;
            uint16_t syn:1;
            uint16_t fin:1;
# else
#  error "Adjust your <bits/endian.h> defines"
# endif
            uint16_t window;
            uint16_t check;
            uint16_t urg_ptr;
        };
    };
}__attribute__((packed));


#define PACK_DATA_BUF_SIZE 1600
typedef struct pack {
    const uint8_t *data;
    int data_len;
    int frame_cap_len_should;
    int l3_offset;
    int l4_offset;
    int with_l2;
    int is_client_from_tun;
    struct list_head node;
    struct pack *tcp_sort_next;
    uint16_t app_data_off;
    uint16_t dport_l; //local machine byte order
    uint16_t fake_pack_win;
    uint16_t l3_proto;
    uint16_t resend_tick_interval;
    uint16_t sport_l; //local machine byte order
    uint32_t app_data_len;
    uint32_t hash;
    uint32_t seq_l;
    uint8_t dir;
    uint8_t is_arp;
    uint8_t is_ipv6;
    uint8_t l4_protocol;
    void *userdata;
    uint8_t buf[PACK_DATA_BUF_SIZE];  //mute be last
} pack_t;

struct my_iphdr {
#if __BYTE_ORDER == __LITTLE_ENDIAN
    uint8_t ihl:4;
    uint8_t version:4;
#else
    uint8_t version:4;
    uint8_t ihl:4;
#endif
    uint8_t tos;
    uint16_t tot_len;
    uint16_t id;
    uint16_t frag_off;
    uint8_t ttl;
    uint8_t protocol;
    uint16_t check;
    uint32_t saddr;
    uint32_t daddr;
}__attribute__((packed));

struct myy_in6_addr {
    uint32_t _addr32[4];
}__attribute__((packed));
struct my_ip6hdr {
    uint32_t flow;
    uint16_t plen;
    uint8_t  nxt;
    uint8_t  hlim;
    struct myy_in6_addr ip6_src;
    struct myy_in6_addr ip6_dst;
}__attribute__((packed));

#define INIT_TCP do { \
    if (pack->app_data_len > 1516 - 14 - 20 - 20) { LOGFEW("bug 123230005\n"); return -100; } \
    pack->sport_l = be16toh(GET_TCPH(pack)->srcport); \
    pack->dport_l = be16toh(GET_TCPH(pack)->dstport); \
    pack->app_data_off = ((uint8_t *)GET_TCPH(pack) + GET_TCPH(pack)->doff*4) - pack->data; \
    if (pack->app_data_off + pack->app_data_len > pack->data_len) { \
        if (local_debug > 0) LLOG("recv a half packet %u byte, protocal indicate %u byte\n", pack->data_len, pack->app_data_off + pack->app_data_len); \
        return -100; \
    } \
    pack->seq_l = tcp_seq_h(pack); \
} while(0)

#define INIT_UDP do { \
    if (pack->app_data_len > 1516 - 14 - 20 - 8) { LOGFEW("bug 123230007\n"); return -100; } \
    pack->sport_l = be16toh(GET_UDPH(pack)->source); \
    pack->dport_l = be16toh(GET_UDPH(pack)->dest); \
    pack->app_data_off = ((uint8_t *)GET_UDPH(pack) + sizeof(struct my_udphdr)) - pack->data; \
    if (pack->app_data_off + pack->app_data_len > pack->data_len) { LOGFEW("bug 123230008\n"); return -100; } \
} while(0)

#define CHECK_GAP do { \
    int need_more_len = ippayload_len > data_len ? ippayload_len - data_len : 0; \
    pack->frame_cap_len_should = pack_len + need_more_len; \
    if (need_more_len) { \
        LOGFEW("ippayload_len %u and cap_rest %u [need more %u]\n", ippayload_len, data_len, need_more_len); \
        return -1; \
    } \
    /*if (data_len > ippayload_len) data_len = ippayload_len; */ \
} while(0)
static inline int init_packinf(pack_t *pack, const uint8_t *pack_data, int pack_len, int with_l2, void *userdata)
{
    int local_debug = 0;
    pack->data_len = pack_len;
    pack->data = (const void *)pack_data;
    pack->userdata = userdata;
    pack->hash = 0;
    pack->app_data_len = 0;
    pack->l4_protocol = 0;
    pack->with_l2 = with_l2;
    uint32_t iphdr_len, ippayload_len;
    struct my_iphdr *iph = NULL;
    const uint8_t *data = pack->data;
    int data_len = pack->data_len;
    if (with_l2) {
        if (data_len <= 14) { LOGFEW("bug, fix me\n"); return -1; }
        data += 12;
        data_len -= 12;
        pack->l3_offset = 14;
    } else {
        goto tag_ipv4;
    }

    pack->l3_proto = be16toh(*(const uint16_t *) data);
ether:
    switch (*(const uint16_t *) data) {
        case 0xa888:
            if (data_len <= 8) { LOGFEW("bug, fix me\n"); return -1; }
            data += 8; data_len -= 8;
            pack->l3_offset += 8;
            goto ether;
        case 0x0081:
            if (data_len <= 4) { LOGFEW("bug, fix me\n"); return -1; }
            data += 4; data_len -= 4;
            pack->l3_offset += 4;
            goto ether;
        case 0x0008:
            if (data_len <= 2) { LOGFEW("bug, fix me\n"); return -1; }
            data += 2; data_len -= 2;
tag_ipv4:
            iph = (struct my_iphdr *)data;
            if (iph->version == 6) goto tag_ipv6;
            pack->is_ipv6 = 0;
            if (iph->version != 4) { LOGFEW("bug 123230003\n"); return -3; }
            if (iph->frag_off & 0x0020) { LOGFEW("see tcp pkg with more fragment flag\n"); return -9; } //more fragment
            iphdr_len = iph->ihl * 4;
            pack->l4_offset = pack->l3_offset + iphdr_len;
            data += iphdr_len;
            if (data_len <= iphdr_len) { LOGFEW("bug, fix me\n"); return -1; }
            data_len -= iphdr_len;
            ippayload_len = be16toh(iph->tot_len) - iphdr_len;
            CHECK_GAP;
            pack->hash = iph->saddr ^ iph->daddr;
            if (0) { }
            else if (iph->protocol == 6) { pack->app_data_len = be16toh(iph->tot_len) - iph->ihl*4 - GET_TCPH(pack)->doff*4; INIT_TCP; }
            else if (iph->protocol == 17) { pack->app_data_len = be16toh(iph->tot_len) - iph->ihl*4 - sizeof(struct my_udphdr); INIT_UDP; }
            else if (iph->protocol == 1) { pack->app_data_len = be16toh(iph->tot_len) - iph->ihl*4 - sizeof(struct my_icmphdr); pack->app_data_off = pack->l3_offset + sizeof(struct my_icmphdr); }
            else if (iph->protocol == 2) { return -33; /*igmp*/ }
            else { LOGFEW("unknown iph->protocol %u\n", iph->protocol); return -5; }
            pack->l4_protocol = iph->protocol;
            break;
        case 0xdd86:
            if (data_len <= 2) { LOGFEW("bug, fix me\n"); return -1; }
            data += 2; data_len -= 2;
tag_ipv6:
            pack->is_ipv6 = 1;
            const struct my_ip6hdr *ip6h = NULL;
            ip6h = (const struct my_ip6hdr *)data;
            pack->l4_offset = pack->l3_offset + sizeof(struct my_ip6hdr);
            data += sizeof(struct my_ip6hdr);
            data_len -= sizeof(struct my_ip6hdr);
            ippayload_len = be16toh(ip6h->plen);
            CHECK_GAP;
            pack->hash = ip6h->ip6_src._addr32[3] ^ ip6h->ip6_dst._addr32[3];
            if (0) { }
            else if (ip6h->nxt == 6)  { pack->app_data_len = be16toh(ip6h->plen) - GET_TCPH(pack)->doff*4; INIT_TCP; }
            else if (ip6h->nxt == 17) { pack->app_data_len = be16toh(ip6h->plen) - sizeof(struct my_udphdr); INIT_UDP; }
            else if (ip6h->nxt == 1)  { pack->app_data_len = be16toh(ip6h->plen); pack->app_data_off = pack->l3_offset + sizeof(struct my_icmphdr); }
            else if (ip6h->nxt == 2) { return -33; /*igmp*/ }
            else if (ip6h->nxt == 0) { return -33; /*ip */ }
            else if (ip6h->nxt == 58){ return -33; /*ipv6_icmp*/ }
            else { LOGFEW("unknow ip6h->nxt %u\n", ip6h->nxt); return -6; }
            pack->l4_protocol = ip6h->nxt;
            break;
        case 0x0608:
            if (data_len <= 2) { LOGFEW("bug, fix me\n"); return -1; }
            data += 2; data_len -= 2;
            if (data_len < 28) {
                static time_t last_ts = 0;
                time_t curr_ts = time(NULL);
                uint32_t d = curr_ts - last_ts;
                static uint32_t count = 0; count++;
                if (d > 10) {
                    last_ts = curr_ts;
                    LOGFEW("rest data_len %d < 28 [packet count %u]\n", data_len, count);
                    //DUMPHEX(pack_data, pack_len, "");
                }
                return -100;
            }
            pack->is_arp = 1;
            /* {
               char buf[64] = "";
               char buf2[64] = "";
               LLOG("wow, get one arp srcmac %02x:%02x:%02x:%02x:%02x:%02x dstmac %02x:%02x:%02x:%02x:%02x:%02x opcode %u sender ip %s client ip %s\n",
               pack->data[6], pack->data[7], pack->data[8], pack->data[9], pack->data[10], pack->data[11],
               pack->data[0], pack->data[1], pack->data[2], pack->data[3], pack->data[4], pack->data[5],
               pack->data[21],
               inet_ntop(AF_INET, (uint32_t *)(pack->data + 28), buf, sizeof(buf)),
               inet_ntop(AF_INET, (uint32_t *)(pack->data + 38), buf2, sizeof(buf2)));
               }*/
            break;
        default:
            //if (memcmp(pack->data, "\x01\x80\xc2\x00\x00\x00", 6) == 0) { LOGFEW("bug 123230013\n"); return -1; }
            if (verbose >= 2) LOGFEW("unknow package with l3_type %u [0x%04X]\n", be16toh(*(const uint16_t *) data), be16toh(*(const uint16_t *) data));
            return -8;
    }
    return 0;
}

#define DUMP_PACK(TAG, pack) do { \
    pack_t *_pack = pack; \
    struct my_iphdr *ip = GET_IPH(_pack); \
    char ip1[64]; char ip2[64]; \
    if (_pack->l4_protocol == 6) { \
        struct my_tcphdr *tcp = GET_TCPH(_pack); \
        LLOG("%s%s:%u --> %s:%u TCP SEQ %11u ACK_SEQ %11u%s%s%s WIN %5u app_data_len %4d\n" \
                , TAG \
                , inet_ntop(AF_INET, &ip->saddr, ip1, sizeof(ip1)) \
                , _pack->sport_l \
                , inet_ntop(AF_INET, &ip->daddr, ip2, sizeof(ip2)) \
                , _pack->dport_l \
                , be32toh(tcp->seq) \
                , be32toh(tcp->ack_seq) \
                , tcp->syn ? " SYN" : "    " \
                , tcp->rst ? " RST" : "" \
                , tcp->fin ? " FIN" : "" \
                , be16toh(tcp->window) \
                , _pack->app_data_len \
                ); \
    } else if (_pack->l4_protocol ==17 ) { \
        LLOG("%s%s:%u --> %s:%u UDP app_data_len %4d\n", \
                TAG, \
                inet_ntop(AF_INET, &ip->saddr, ip1, sizeof(ip1)), _pack->sport_l, inet_ntop(AF_INET, &ip->daddr, ip2, sizeof(ip2)), _pack->dport_l, \
                _pack->app_data_len); \
    } else if (_pack->is_arp > 0) { \
        uint8_t *arp = (void *)GET_IPH(_pack); \
        if (arp[7] == 1 && _pack->data_len >= 14 + 28) { \
            LLOG("%sarp request sender ip %s client ip %s app_data_len %4d\n", \
                    TAG, \
                    inet_ntop(AF_INET, arp + 14, ip1, sizeof(ip1)), inet_ntop(AF_INET, arp + 24, ip2, sizeof(ip2)), \
                    _pack->app_data_len); \
        } else if (arp[7] == 2 && _pack->data_len >= 14 + 28) { \
            LLOG("%sarp request sender ip %s client ip %s app_data_len %4d\n", \
                    TAG, \
                    inet_ntop(AF_INET, arp + 14, ip1, sizeof(ip1)), inet_ntop(AF_INET, arp + 24, ip2, sizeof(ip2)), \
                    _pack->app_data_len); \
        } else { \
            LLOG("%sarp format error app_data_len %4d\n", \
                    TAG, \
                    _pack->app_data_len); \
        }\
    } else if (_pack->l4_protocol == 1) { \
        LLOG("%s%s:%u --> %s:%u ICMP app_data_len %4d\n", \
                TAG, \
                inet_ntop(AF_INET, &ip->saddr, ip1, sizeof(ip1)), _pack->sport_l, inet_ntop(AF_INET, &ip->daddr, ip2, sizeof(ip2)), _pack->dport_l, \
                _pack->app_data_len); \
    } else { \
        LLOG("%s%s:%u --> %s:%u UNKNOWN PACKET l3_proto %u [0x%04X] app_data_len %4d\n", \
                TAG, \
                inet_ntop(AF_INET, &ip->saddr, ip1, sizeof(ip1)), _pack->sport_l, inet_ntop(AF_INET, &ip->daddr, ip2, sizeof(ip2)), _pack->dport_l, \
                _pack->l3_proto, _pack->l3_proto, _pack->app_data_len); \
    } \
} while(0)

#define DUMP_PKG(TAG, data, len, with_l2) do { pack_t __pack; if (init_packinf(&__pack, data, len, with_l2, NULL) == 0) DUMP_PACK(TAG, &__pack); } while(0)
#define DUMP_PKG_FEW(TAG, data, len, with_l2, intv) do { static time_t l = 0; time_t c = time(NULL); if (c - l >= intv) { DUMP_PKG(TAG, data, len, with_l2); l = c; } } while(0)
#define DUMP_PACK_FEW(TAG, pack, intv) do { static time_t l = 0; time_t c = time(NULL); if (c - l >= intv) { DUMP_PACK(TAG, pack); l = c; } } while(0)

#define DUMP_PKG_GREEN(TAG, _data, _len, with_l2) do { printf("\033[32m"); DUMP_PKG(TAG, _data, _len, with_l2); printf("\033[0m"); } while(0)
#define DUMP_PKG_YELLOW(TAG, _data, _len, with_l2) do { printf("\033[33m"); DUMP_PKG(TAG, _data, _len, with_l2); printf("\033[0m"); } while(0)

static inline uint16_t csum_fold(uint32_t cksum)
{
    cksum  = (cksum >> 16) + (cksum & 0xffff);
    cksum += (cksum >> 16);
    return ~(cksum & 0xffff);
}

/**
 * ip_fast_csum - Compute the IPv4 header checksum efficiently.
 * iph: ipv4 header
 * ihl: length of header / 4
 */
static inline uint16_t ip_fast_csum(const void *iph, uint32_t ihl)
{
    uint32_t sum;
    asm(    "  movl (%1), %0\n"
            "  subl $4, %2\n"
            "  jbe 2f\n"
            "  addl 4(%1), %0\n"
            "  adcl 8(%1), %0\n"
            "  adcl 12(%1), %0\n"
            "1: adcl 16(%1), %0\n"
            "  lea 4(%1), %1\n"
            "  decl %2\n"
            "  jne      1b\n"
            "  adcl $0, %0\n"
            "  movl %0, %2\n"
            "  shrl $16, %0\n"
            "  addw %w2, %w0\n"
            "  adcl $0, %0\n"
            "  notl %0\n" "2:"
            /* Since the input registers which are loaded with iph and ihl
               are modified, we must also specify them as outputs, or gcc
               will assume they contain their original values. */
            :    "=r"(sum), "=r"(iph), "=r"(ihl)
            :    "1"(iph), "2"(ihl)
                     :    "memory");
    return (uint16_t) sum;
}

/**
 * csum_tcpup_nofold - Compute an IPv4 pseudo header checksum.
 * @saddr: source address
 * @daddr: destination address
 * @len: length of packet
 * @proto: ip protocol of packet
 * @sum: initial sum to be added in (32bit unfolded)
 *
 * Returns the pseudo header checksum the input data. Result is 32bit unfolded.
 */
static inline uint32_t csum_tcpudp_nofold(uint32_t saddr, uint32_t daddr, uint16_t len, uint16_t proto, uint32_t sum)
{
    asm(    "  addl %1, %0\n"
            "  adcl %2, %0\n"
            "  adcl %3, %0\n"
            "  adcl $0, %0\n" : "=r"(sum) : "g"(daddr), "g"(saddr), "g"((len + proto) << 8), "0"(sum));
    return sum;
}

/**
 * csum_tcpup_magic - Compute an IPv4 pseudo header checksum.
 * @saddr: source address
 * @daddr: destination address
 * @len: length of packet
 * @proto: ip protocol of packet
 * @sum: initial sum to be added in (32bit unfolded)
 *
 * Returns the 16bit pseudo header checksum the input data already
 * complemented and ready to be filled in.
 */
static inline uint16_t csum_tcpudp_magic(uint32_t saddr, uint32_t daddr, unsigned short len, unsigned short proto, uint32_t sum)
{
    return csum_fold(csum_tcpudp_nofold(saddr, daddr, len, proto, sum));
}

static inline unsigned short from32to16(unsigned a)
{
    unsigned short b = a >> 16;
    asm(    "addw %w2,%w0\n"
            "adcw $0,%w0\n":"=r"(b)
            : "0"(b), "r"(a));
    return b;
}

static inline unsigned add32_with_carry(unsigned a, unsigned b)
{
    asm(    "addl %2,%0\n\t"
            "adcl $0,%0":"=r"(a)
            : "0"(a), "r"(b));
    return a;
}

static inline unsigned do_csum(const unsigned char *buff, unsigned len)
{
    unsigned odd, count;
    unsigned long long result = 0;

    if (len == 0) return result;
    odd = 1 & (unsigned long)buff;
    if (odd) {
        result = *buff << 8;
        len--;
        buff++;
    }
    count = len >> 1;    /* nr of 16-bit words.. */
    if (count) {
        if (2 & (unsigned long)buff) {
            result += *(const unsigned short *)buff;
            count--;
            len -= 2;
            buff += 2;
        }
        count >>= 1;    /* nr of 32-bit words.. */
        if (count) {
            unsigned long zero;
            unsigned count64;
            if (4 & (unsigned long)buff) {
                result += *(const unsigned int *)buff;
                count--;
                len -= 4;
                buff += 4;
            }
            count >>= 1;    /* nr of 64-bit words.. */

            /* main loop using 64byte blocks */
            zero = 0;
            count64 = count >> 3;
            while (count64) {
                asm(    "addq 0*8(%[src]),%[res]\n\t"
                        "adcq 1*8(%[src]),%[res]\n\t"
                        "adcq 2*8(%[src]),%[res]\n\t"
                        "adcq 3*8(%[src]),%[res]\n\t"
                        "adcq 4*8(%[src]),%[res]\n\t"
                        "adcq 5*8(%[src]),%[res]\n\t"
                        "adcq 6*8(%[src]),%[res]\n\t"
                        "adcq 7*8(%[src]),%[res]\n\t"
                        "adcq %[zero],%[res]":[res]
                        "=r"(result)
                        : [src] "r"(buff),[zero] "r"(zero), "[res]"(result));
                buff += 64;
                count64--;
            }

            /* last up to 7 8byte blocks */
            count %= 8;
            while (count) {
                asm(    "addq %1,%0\n"
                        "adcq %2,%0\n":"=r"(result)
                        : "m"(*(const unsigned long *)buff), "r"(zero), "0"(result));
                --count;
                buff += 8;
            }
            result = add32_with_carry(result >> 32, result & 0xffffffff);

            if (len & 4) {
                result += *(const unsigned int *)buff;
                buff += 4;
            }
        }
        if (len & 2) {
            result += *(const unsigned short *)buff;
            buff += 2;
        }
    }
    if (len & 1) result += *buff;
    result = add32_with_carry(result >> 32, result & 0xffffffff);
    if (odd) {
        result = from32to16(result);
        result = ((result >> 8) & 0xff) | ((result & 0xff) << 8);
    }
    return result;
}

static inline uint32_t csum_partial(const void *buff, int len, uint32_t sum)
{
    return (uint32_t) add32_with_carry(do_csum(buff, len), (uint32_t) sum);
}

static inline uint32_t in_cksum(uint8_t *addr, uint32_t len)
{
    uint32_t sum = 0;
    uint8_t *p = addr;
    while (len > 1) {
        uint16_t n; memcpy(&n, p, 2); p += 2;
        sum += n;
        len -= 2;
    }
    if (len == 1) {
        union { uint16_t s; uint8_t b[2]; } pad;
        pad.b[0] = *p;
        pad.b[1] = 0;
        sum += pad.s;
    }
    return sum;
}

static inline void update_tcp_pack_checksum(uint8_t *buf, int ip_offset)
{
    struct my_iphdr *ih = (struct my_iphdr *)(buf + ip_offset);
    struct my_tcphdr *th = (struct my_tcphdr *)(buf + ip_offset + sizeof(struct my_iphdr));
    int packet_data_len = be16toh(ih->tot_len) - sizeof(struct my_iphdr) - sizeof(struct my_tcphdr);
    th->check = 0;
#ifdef __x86_64__
    th->check = csum_tcpudp_magic(ih->saddr, ih->daddr, sizeof(struct my_tcphdr) + packet_data_len, ih->protocol, csum_partial(th, sizeof(struct my_tcphdr) + packet_data_len, 0));
#else
    th->check = csum_fold(in_cksum((void *)&ih->saddr, 8) + htobe16(6) + htobe16(sizeof(struct my_tcphdr) + packet_data_len) + in_cksum((void *)th, sizeof(struct my_tcphdr) + packet_data_len));
#endif
}

static inline void update_pack_ack_seq_and_win(uint8_t *buf, uint32_t ack_seq, uint16_t win, int ip_offset)
{
    struct my_tcphdr *th = (struct my_tcphdr *)(buf + ip_offset + sizeof(struct my_iphdr));
    th->ack_seq = htobe32(ack_seq);
    th->window = htobe16(win);
    update_tcp_pack_checksum(buf, ip_offset);
}

static void updata_ip_checksum(uint8_t *packet, int ip_headoffset)
{
    struct my_iphdr *ih = (struct my_iphdr *)(packet + ip_headoffset);
#ifdef __x86_64__
    ih->check = ip_fast_csum((unsigned char *)ih, ih->ihl);
#else
    ih->check = csum_fold(in_cksum((void *)ih, (ih->ihl * 4)));
#endif
}

static inline int build_tcp_packet(uint8_t *buf, const void *src_mac, const void *dst_mac,
        uint32_t src_ip, uint32_t dst_ip, uint16_t src_port, uint16_t dst_port,
        uint32_t seq, uint32_t ack_seq, int syn, int ack, int fin, int rst, int is_keepalive, uint16_t win,
        const void *packet_data, int packet_data_len,
        const void *packet_data2, int packet_data_len2, void *tail, int tail_len)
{
    int data_len = 14 + sizeof(struct my_iphdr) + sizeof(struct my_tcphdr) + packet_data_len + packet_data_len2;
    struct my_iphdr *ih = (struct my_iphdr *)(buf + 14);
    struct my_tcphdr *th = (struct my_tcphdr *)(buf + 14 + sizeof(struct my_iphdr));

    memset(buf, 0, data_len);
    memcpy(buf + 6, src_mac, 6);
    memcpy(buf,     dst_mac, 6);
    memcpy(buf + 12, "\x08\x00", 2);
    ih->version = 4;
    ih->ihl = sizeof(struct my_iphdr) / 4;
    uint16_t n = sizeof(struct my_iphdr) + sizeof(struct my_tcphdr) + packet_data_len + packet_data_len2 + tail_len;
    ih->tot_len = htobe16(n);
    ih->frag_off = htobe16(0x4000);
    ih->ttl = 64;
    ih->protocol = 6;
    ih->saddr = src_ip;
    ih->daddr = dst_ip;
    th->srcport = src_port;
    th->dstport = dst_port;
    th->doff = sizeof(struct my_tcphdr) / 4;
    th->ack = ack;
    th->fin = fin;
    th->rst = rst;
    th->seq = htobe32(seq - is_keepalive);
    th->syn = syn;
    memcpy((char *)(th + 1), packet_data, packet_data_len);
    if (packet_data_len2 > 0) memcpy((char *)(th + 1) + packet_data_len, packet_data2, packet_data_len2);
    memcpy((char *)(th + 1) + packet_data_len + packet_data_len2, tail, tail_len);
    ih->check = 0;
    updata_ip_checksum(buf, 14);
    //th->window = htobe16(win);
    update_pack_ack_seq_and_win(buf, ack_seq, win, 14);

    return data_len + tail_len;
}

#ifdef __x86_64__
static inline uint16_t csum_ipv6_magic(const void *saddr, const void *daddr, uint32_t len, uint8_t proto, uint16_t sum)
{
    uint64_t rest, sum64;
    rest = (uint64_t)htobe32(len) + (uint64_t)htobe16(proto) + (uint64_t)sum;
    asm(    "   addq (%[saddr]),%[sum]\n"
            "   adcq 8(%[saddr]),%[sum]\n"
            "   adcq (%[daddr]),%[sum]\n"
            "   adcq 8(%[daddr]),%[sum]\n"
            "   adcq $0,%[sum]\n"
            : [sum] "=r" (sum64)
            : "[sum]" (rest), [saddr] "r" (saddr), [daddr] "r" (daddr));
    return csum_fold((uint32_t)add32_with_carry(sum64 & 0xffffffff, sum64>>32));
}

static inline int build_tcp_packet_v6(uint8_t *buf, const uint8_t *src_mac, const uint8_t *dst_mac,
        void *src_ip, void *dst_ip, uint16_t src_port, uint16_t dst_port,
        uint32_t seq, uint32_t ack_seq, int syn, int ack, int fin, int rst, unsigned win,
        const void *packet_data, int packet_data_len, const void *tail, int tail_len)
{
    int data_len = 14 + sizeof(struct my_ip6hdr) + sizeof(struct my_tcphdr) + packet_data_len;
    struct my_ip6hdr *ih = (struct my_ip6hdr *)(buf + 14);
    struct my_tcphdr *th = (struct my_tcphdr *)(buf + 14 + sizeof(struct my_ip6hdr));

    memset(buf, 0, data_len);
    memcpy(buf + 6, src_mac, 6);
    memcpy(buf,     dst_mac, 6);
    memcpy(buf + 12, "\x86\xdd", 2);
    ih->flow = htobe32(0x60000000);
    ih->plen = htobe16(sizeof(struct my_tcphdr) + packet_data_len);
    ih->nxt = 6;
    ih->hlim = 62;
    memcpy(&ih->ip6_src, src_ip, 16);
    memcpy(&ih->ip6_dst, dst_ip, 16);
    th->srcport = src_port;
    th->dstport = dst_port;
    th->doff = sizeof(struct my_tcphdr) / 4;
    th->ack = ack;
    th->fin = fin;
    th->rst = rst;
    th->seq = seq;
    th->syn = syn;
    th->ack_seq = ack_seq;
    th->window = htobe16((win + 255)/256);
    memcpy((char *)(th + 1), packet_data, packet_data_len);
    memcpy((char *)(th + 1) + packet_data_len, tail, tail_len);
    th->check = csum_ipv6_magic(src_ip, dst_ip, sizeof(struct my_tcphdr), 6, csum_partial(th, sizeof(struct my_tcphdr) + packet_data_len, 0));
    return data_len + tail_len;
}
#endif

static inline int build_udp_packet(uint8_t *buf, const void *src_mac, const void *dst_mac,
        uint32_t src_ip, uint32_t dst_ip, uint16_t src_port, uint16_t dst_port,
        const void *packet_data, int packet_data_len, void *tail, int tail_len)
{
    int data_len = 14 + sizeof(struct my_iphdr) + sizeof(struct my_udphdr) + packet_data_len;
    struct my_iphdr *ih = (struct my_iphdr *)(buf + 14);
    struct my_udphdr *uh = (struct my_udphdr *)(buf + 14 + sizeof(struct my_iphdr));

    memset(buf, 0, data_len);
    memcpy(buf + 6, src_mac, 6);
    memcpy(buf,     dst_mac, 6);
    memcpy(buf + 12, "\x08\x00", 2);
    ih->version = 4;
    ih->ihl = sizeof(struct my_iphdr) / 4;
    ih->tot_len = htobe16(sizeof(struct my_iphdr) + sizeof(struct my_udphdr) + packet_data_len + tail_len);
    ih->frag_off = htobe16(0x4000);
    ih->ttl = 64;
    ih->protocol = 17;
    ih->saddr = src_ip;
    ih->daddr = dst_ip;
    uh->source = src_port;
    uh->dest = dst_port;
    uh->len = htobe16(8 + packet_data_len);
    memcpy((char *)(uh + 1), packet_data, packet_data_len);
    memcpy((char *)(uh + 1) + packet_data_len, tail, tail_len);
    ih->check = 0;
#ifdef __x86_64__
    ih->check = ip_fast_csum(ih, ih->ihl);
#else
    ih->check = csum_fold(in_cksum((void *)ih, (ih->ihl * 4)));
#endif
    uh->check = 0;
#ifdef __x86_64__
    uh->check = csum_tcpudp_magic(ih->saddr, ih->daddr, sizeof(struct my_udphdr) + packet_data_len, ih->protocol, csum_partial(uh, sizeof(struct my_udphdr) + packet_data_len, 0));
#else
    uh->check = csum_fold(in_cksum((void *)&ih->saddr, 8) + htobe16(17) + htobe16(sizeof(struct my_udphdr) + packet_data_len) + in_cksum((void *)uh, sizeof(struct my_udphdr) + packet_data_len));
#endif

    return data_len + tail_len;
}

static inline uint16_t icmp_checksum(uint8_t *buffer, int size)
{
    uint32_t cksum=0;
    uint8_t *p = buffer;
    while (size > 1) {
        uint16_t n; memcpy(&n, p, 2); p += 2;
        cksum += n;
        size -= 2;
    }
    if (size) cksum += *(uint8_t *)buffer;
    return csum_fold(cksum);
}

static inline int build_icmp_echo_reply_packet(uint8_t *buf,
        const void *src_mac, const void *dst_mac, uint32_t src_ip,
        uint32_t dst_ip, uint8_t *data, int icmp_len)
{
    int data_len = 14 + sizeof(struct my_iphdr) + icmp_len;
    struct my_iphdr *ih = (struct my_iphdr *)(buf + 14);
    struct my_icmphdr *icmp = (struct my_icmphdr *)(buf + 14 + sizeof(struct my_iphdr));

    memset(buf, 0, data_len);
    memcpy(buf + 6, src_mac, 6);
    memcpy(buf,     dst_mac, 6);
    memcpy(buf + 12, "\x08\x00", 2);
    ih->version = 4;
    ih->ihl = sizeof(struct my_iphdr) / 4;
    ih->tot_len = htobe16(sizeof(struct my_iphdr) + icmp_len);
    ih->frag_off = htobe16(0x4000);
    ih->ttl = 64;
    ih->protocol = 1;
    ih->saddr = src_ip;
    ih->daddr = dst_ip;
    memcpy((char *)(icmp), data, icmp_len);
    memcpy(icmp, "\x00", 1);
    ih->check = 0;
    ih->check = csum_fold(in_cksum((void *)ih, sizeof(*ih)));
    icmp->checksum = 0;
    icmp->checksum = icmp_checksum((void *)icmp, icmp_len);
    return data_len;
}

#if 0
{
    FILE *fp = fopen("1.pcap", "w");

    struct my_pcap_filehdr {
        uint32_t magic;
        uint16_t minor_version;
        uint16_t magot_version;
        uint32_t timezone;
        uint32_t sigflags;
        uint32_t snaplen;
        uint32_t linktype;
    };
    struct my_pcap_filehdr header = {
        0xa1b2c3d4,
        0x0002,
        0x0004,
        0x00000000,
        0x00000000,
        0x00040000,
        0x00000001,
    };
    fwrite(&header, 1, sizeof(header), fp);



    struct my_pcap_pkthdr {
        uint32_t sec;
        uint32_t usec;
        uint32_t caplen;
        uint32_t len;
    } pcap_frame_head;
    struct timeval  tv;
    gettimeofday(&tv, NULL);
    pcap_frame_head.sec = (uint32_t)tv.tv_sec;
    pcap_frame_head.usec = (uint32_t)tv.tv_usec;

    pcap_frame_head.caplen = pack->data_len;//+2+tail_len;
    pcap_frame_head.len    = pack->data_len;;//+2+tail_len;
    fwrite(&pcap_frame_head, 1, sizeof(pcap_frame_head), fp);
    fwrite(pack->data, 1, pack->data_len, fp);

    pcap_frame_head.caplen = ret;//+2+tail_len;
    pcap_frame_head.len    = ret;//+2+tail_len;
    fwrite(&pcap_frame_head, 1, sizeof(pcap_frame_head), fp);
    fwrite(buf, 1, ret, fp);

    fclose(fp);
    exit(1);
}
#endif
#endif
